package com.blocks.blocks;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.ScriptException;

import com.blocks.Clusters;
import com.blocks.ScriptBlocksApplication;
import com.blocks.controller.BlocksCollisionShape;
import com.blocks.gui.BlockListGroup;
import com.blocks.model.BlockBox;
import com.blocks.model.BlockSides;
import com.blocks.script.ScriptUtils;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.PhysicsControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Image.Format;

public class Block extends Node {
	private ColorRGBA color = ColorRGBA.Gray;
	private Block[] blockArray = new Block[6];
	Material material;
	private boolean powered = false;
	boolean focus = false;
	private Long masterId = null;
	ScriptEvaluator scriptEvaluator;
	String defaultTexture = "Textures/Starter.tga";
	Material defaultMaterial = new Material(ScriptBlocksApplication
			.getInstance().getAssetManager(),
			"Common/MatDefs/Misc/Unshaded.j3md");
	String scriptName = null;
	private BlockSides blockSides = null; // this is used to tell where this
											// block is attached to its Parents
											// Block
	// int frequency = 0;
	private Map<BlockSides, Integer> frequencySide = new HashMap<BlockSides, Integer>();

	boolean customFrequency = false;
	float deathTimer = 0; // ZERO means not used
	float hitPoints = 1; // ZERO means not used

	Texture focusTexture = null;
	Texture unFocusTexture = null;

	float textureSectionsX = 16.0f;
	float textureSectionsY = 16.0f;
	float textureRow = 0.0f;
	float textureColumn = 0.0f;

	private BlockBox blockBox = null;

	private Block blockAbove; // 1
	private Block blockBelow; // 6
	private Block blockLeft; // 3
	private Block blockRight; // 4
	private Block blockFront; // 2
	private Block blockBack; // 5

	// potential sanity check to save some cycles
	// everytime we do a traverse of a block list we'll set these to true
	// after we're done we'll set them all back
	// not sure if this will help at all, but if we have some blocks
	// that have a powerlimit of 6 it could save a lot of cycles
	private boolean traversed = false;

	private Clusters clust = null;

	private Vector3f movement = Vector3f.ZERO;

	private boolean hudBlock = false;

	// What list the block will show up on on the inventory gui screen.
	BlockListGroup blockListGroup = BlockListGroup.LOGIC;

	Map<String, Object> parameters = new HashMap<String, Object>();

	float updateFrequencyLastTime = 0;

	public Block() {
		frequencySide.put(BlockSides.SIDE_FRONT, 0);
		frequencySide.put(BlockSides.SIDE_BACK, 0);
		frequencySide.put(BlockSides.SIDE_ABOVE, 0);
		frequencySide.put(BlockSides.SIDE_BELOW, 0);
		frequencySide.put(BlockSides.SIDE_LEFT, 0);
		frequencySide.put(BlockSides.SIDE_RIGHT, 0);
	}

	public Block(String name, Long masterId) {
		super(name);
		frequencySide.put(BlockSides.SIDE_FRONT, 0);
		frequencySide.put(BlockSides.SIDE_BACK, 0);
		frequencySide.put(BlockSides.SIDE_ABOVE, 0);
		frequencySide.put(BlockSides.SIDE_BELOW, 0);
		frequencySide.put(BlockSides.SIDE_LEFT, 0);
		frequencySide.put(BlockSides.SIDE_RIGHT, 0);
		this.masterId = masterId;
	}

	public String getDefaultTexture() {
		return defaultTexture;
	}

	public void setDefaultTexture(String defaultTexture) {
		this.defaultTexture = defaultTexture;
	}

	public String getFocusTextureName() {
		return "Textures/StarterAlpha.tga";

	}

	public String getUnFocusTextureName() {
		return "Textures/StarterUnFocus.tga";
	}

	public Texture getFocusTexture() {
		if (focusTexture == null) {
			focusTexture = ScriptBlocksApplication.getInstance()
					.getAssetManager().loadTexture(getFocusTextureName());
		}
		return focusTexture;
	}

	public Texture getUnFocusTexture() {
		if (unFocusTexture == null) {
			unFocusTexture = ScriptBlocksApplication.getInstance()
					.getAssetManager().loadTexture(getUnFocusTextureName());
		}
		return unFocusTexture;
	}

	public void setFocusTexture(Texture focusTexture) {
		this.focusTexture = focusTexture;
	}

	public void setUnFocusTexture(Texture unFocusTexture) {
		this.unFocusTexture = unFocusTexture;
	}

	public Material getDefaultMaterial() {
		resetTexture();
		return defaultMaterial;
	}

	public void resetTexture() {
		defaultMaterial.setTexture("ColorMap",
				ScriptBlocksApplication.getInstance().getAssetManager()
						.loadTexture(getDefaultTexture()));
	}

	public boolean isTraversed() {
		return traversed;
	}

	public Block getBlockAbove() {
		return blockAbove;
	}

	public void setBlockAbove(Block blockAbove) {
		this.blockAbove = blockAbove;
	}

	public Block getBlockBelow() {
		return blockBelow;
	}

	public void setBlockBelow(Block blockBelow) {
		this.blockBelow = blockBelow;
	}

	public Block getBlockLeft() {
		return blockLeft;
	}

	public void setBlockLeft(Block blockLeft) {
		this.blockLeft = blockLeft;
	}

	public Block getBlockRight() {
		return blockRight;
	}

	public void setBlockRight(Block blockRight) {
		this.blockRight = blockRight;
	}

	public Block getBlockFront() {
		return blockFront;
	}

	public void setBlockFront(Block blockFront) {
		this.blockFront = blockFront;
	}

	public Block getBlockBack() {
		return blockBack;
	}

	public void setBlockBack(Block blockBack) {
		this.blockBack = blockBack;
	}

	public void setTraversed(boolean traversed) {
		this.traversed = traversed;
	}

	public Clusters getClust() {
		return clust;
	}

	public void setClust(Clusters clust) {
		this.clust = clust;
	}

	public Block[] getBlockArray() {
		return blockArray;
	}

	public void setBlockArray(Block[] blockArray) {
		this.blockArray = blockArray;
	}

	public void init() {
		clean();
		if (getScriptName() != null) {
			ScriptEvaluator scriptEvaluator = new ScriptEvaluator();
			this.setScriptEvaluator(scriptEvaluator);
			try {
				scriptEvaluator.evalutate(ScriptBlocksApplication.getInstance()
						.getScriptDir() + getScriptName(), this);
			} catch (Exception ev) {
				ev.printStackTrace();
			}
		}
		if (this.scriptEvaluator != null) {
			scriptEvaluator.setupScript();
		}

		blockBox = new BlockBox(Vector3f.ZERO, 1, 1, 1, getTextureSectionsX(),
				getTextureSectionsY(), getTextureRow(), getTextureColumn());
		Geometry quad = new Geometry("box", blockBox);

		this.setMaterial(this.getDefaultMaterial());
		quad.setMaterial(this.getMaterial());
		this.attachChild(quad);
		
		this.setShadowMode(ShadowMode.Cast);
		

	}

	public void updatePower(boolean powered) {
		this.setPowered(powered);
		for (Spatial spatial : this.getChildren()) {
			if (Block.class.isAssignableFrom(spatial.getClass())) {
				Block childBlock = (Block) spatial;
				childBlock.updatePower(powered);
			}
		}
	}

	public Material getMaterial() {
		return material;
	}

	public void setMaterial(Material material) {
		this.material = material;
	}

	public void setTexture(String texture) {
		Material material = this.getMaterial();
		material.setTexture("ColorMap", ScriptBlocksApplication.getInstance()
				.getAssetManager().loadTexture(texture));
	}

	public void update() throws ScriptException {

	}

	public void clean() {
		this.setColor(ColorRGBA.Gray);
	}

	@Override
	public void updateLogicalState(float tpf) {

		super.updateLogicalState(tpf);
		if (isFocus()) {
			addFocus();
		} else {
			removeFocus();
		}
		if ((this.isPowered() && updateFrequencyLastTime > ScriptBlocksApplication.BLOCK_UPDATE_TIME)
				|| (!this.isPowered() && updateFrequencyLastTime > ScriptBlocksApplication.UNPOWERED_BLOCK_UPDATE_TIME)) {

			if (this.isPowered()) {
				setColor(ColorRGBA.White);
				if (!customFrequency) {

					this.setAllFrequency(getParentFrequency(this
							.getBlockSides()));

				}

			} else {
				setAllFrequency(0);
				setColor(ColorRGBA.Gray);

			}
			if (getDeathTimer() > 0) {
				setDeathTimer(this.getDeathTimer() - tpf);
			} else if (getDeathTimer() < 0) {
				destroy();
			}
			if (getHitPoints() < 0) {
				destroy();
			}
			if (this.scriptEvaluator != null) {
				scriptEvaluator.loopScript(tpf);
			}
			updateFrequencyLastTime = 0;
		} else {
			updateFrequencyLastTime = updateFrequencyLastTime + tpf;
		}
	}

	public int getParentFrequency(BlockSides blockSides) {
		return ((Block) this.getParent()).getFrequency(blockSides);
	}

	public void addFocus() {
		this.getMaterial().setTexture("LightMap", this.getFocusTexture());
	}

	public void removeFocus() {
		this.getMaterial().setTexture("LightMap", this.getUnFocusTexture());
	}

	public void markBlock() {
		this.setFocus(true);
	}

	public void unmarkBlock() {
		this.setFocus(false);
	}

	public Vector3f getMovement() {
		return movement;
	}

	public void setMovement(Vector3f movement) {
		this.movement = movement;
	}

	public boolean isFocus() {
		return focus;
	}

	public void setFocus(boolean focus) {
		this.focus = focus;
	}

	public boolean isPowered() {
		return powered;
	}

	public void setPowered(boolean powered) {
		this.powered = powered;
	}

	public void use() {
		if (this.scriptEvaluator != null) {
			scriptEvaluator.useScript();
		}
	}

	public int getChildCount() {
		int count = 1;
		for (Spatial spatial : this.getChildren()) {
			if (Block.class.isAssignableFrom(spatial.getClass())) {
				Block childBlock = (Block) spatial;
				count = count + childBlock.getChildCount();
			}
		}
		return count;
	}

	public Long getMasterId() {
		return masterId;
	}

	public void setMasterId(Long masterId) {
		this.masterId = masterId;

	}

	public ScriptEvaluator getScriptEvaluator() {
		return scriptEvaluator;
	}

	public void setScriptEvaluator(ScriptEvaluator scriptEvaluator) {
		this.scriptEvaluator = scriptEvaluator;
	}

	public String getScriptName() {
		return scriptName;
	}

	public void setScriptName(String scriptName) {
		this.scriptName = scriptName;
	}

	public BlockSides getBlockSides() {
		return blockSides;
	}

	public void setBlockSides(BlockSides blockSides) {
		this.blockSides = blockSides;
	}

	// public int getFrequency() {
	// return frequency;
	// }
	//
	// public void setFrequency(int frequency) {
	// this.frequency = frequency;
	// }

	public int getFrequency(BlockSides blocksides) {
		try {
			return frequencySide.get(blocksides).intValue();
		} catch (NullPointerException e) {
		}
		return 0;
	}

	public void setAllFrequency(int value) {
		setFrequency(BlockSides.SIDE_FRONT, value);
		setFrequency(BlockSides.SIDE_BACK, value);
		setFrequency(BlockSides.SIDE_ABOVE, value);
		setFrequency(BlockSides.SIDE_BELOW, value);
		setFrequency(BlockSides.SIDE_LEFT, value);
		setFrequency(BlockSides.SIDE_RIGHT, value);
	}

	public void setFrequency(BlockSides side, int value) {
		frequencySide.put(side, new Integer(value));
	}

	public boolean isCustomFrequency() {
		return customFrequency;
	}

	public void setCustomFrequency(boolean customFrequency) {
		this.customFrequency = customFrequency;
	}

	public float getDeathTimer() {
		return deathTimer;
	}

	public void setDeathTimer(float deathTimer) {
		this.deathTimer = deathTimer;
	}

	public float getHitPoints() {
		return hitPoints;
	}

	public void setHitPoints(float hitPoints) {
		this.hitPoints = hitPoints;
	}

	@SuppressWarnings("unchecked")
	public void destroy() {
		this.setScriptEvaluator(null);
		if (this instanceof MasterBlock) {
			
				this.setDeathTimer(0);
	
				ScriptBlocksApplication.getInstance().getMasterBlockMap()
						.remove(this);
				ScriptBlocksApplication.getInstance().getRootNode()
						.detachChild(this);
				List<Spatial> childrenList = new ArrayList<Spatial>(getChildren());
				for (Spatial spatial : childrenList) {
					this.detachChild(spatial);
				}
	
				this.setScriptEvaluator(null);
			
		} else {
			((Block) this.getParent()).detachChild(this);
		}

	}

	@SuppressWarnings("unchecked")
	public void updateMasterId(Long masterId) {
		if (!(this instanceof MasterBlock)) {
			this.setMasterId(masterId);
		}
		List<Spatial> childrenList = new ArrayList<Spatial>(getChildren());
		for (Spatial spatial : childrenList) {
			if (Block.class.isAssignableFrom(spatial.getClass())) {
				Block childBlock = (Block) spatial;
				childBlock.updateMasterId(masterId);
			}
		}
	}

	public float getTextureSectionsX() {
		return textureSectionsX;
	}

	public float getTextureSectionsY() {
		return textureSectionsY;
	}

	public float getTextureRow() {
		return textureRow;
	}

	public float getTextureColumn() {
		return textureColumn;
	}

	public float getFocusTextureRow() {
		return 0.0f;
	}

	public float getFocusTextureColumn() {
		return 1.0f;
	}

	public float getUnFocusTextureRow() {
		return textureRow;
	}

	public float getUnFocusTextureColumn() {
		return textureColumn;
	}

	public void setTextureSectionsX(float textureSectionsX) {
		this.textureSectionsX = textureSectionsX;
	}

	public void setTextureSectionsY(float textureSectionsY) {
		this.textureSectionsY = textureSectionsY;
	}

	public void setTextureRow(float textureRow) {
		this.textureRow = textureRow;
	}

	public void setTextureColumn(float textureColumn) {
		this.textureColumn = textureColumn;
	}

	public RigidBodyControl getRigidBodyControl() {
		return this.getControl(RigidBodyControl.class);
	}

	public Map<BlockSides, Integer> getFrequencySide() {
		return frequencySide;
	}

	public void setFrequencySide(Map<BlockSides, Integer> frequencySide) {
		this.frequencySide = frequencySide;
	}

	public boolean isHudBlock() {
		return hudBlock;
	}

	public void setHudBlock(boolean hudBlock) {
		this.hudBlock = hudBlock;
	}

	public void setColor(ColorRGBA colorRGBA) {
		if (this.color == null || !this.color.equals(colorRGBA)) {
			this.color = colorRGBA;
			this.getMaterial().setColor("Color", color);
		}
	}

	public BlockListGroup getBlockListGroup() {
		return blockListGroup;
	}

	public void setBlockListGroup(BlockListGroup blockListGroup) {
		this.blockListGroup = blockListGroup;
	}

	public Map<String, Object> getParameters() {
		return parameters;
	}

	public void setParameters(Map<String, Object> parameters) {
		this.parameters = parameters;
	}

	@Override
	public int hashCode() {

		return super.hashCode() + this.getMasterId().intValue();
	}

	public String getExportVariable() {
		return this.getClass().getSimpleName() + Math.abs(this.hashCode());
	}

	public String scriptExport() {

		StringBuffer sb = new StringBuffer();
		String blockVariable = getExportVariable();
		Vector3f blockTranslation = this.getLocalTranslation();
		sb.append("var " + blockVariable + " = new "
				+ this.getClass().getSimpleName() + "(\"" + getExportVariable()
				+ "\", new Long(" + this.getMasterId() + "));\n");
		if (this.getBlockSides() != null) {
			sb.append(blockVariable + ".setBlockSides(BlockSides."
					+ this.getBlockSides() + ");\n");
		}
		if (isCustomFrequency()) {
			sb.append(blockVariable + ".setCustomFrequency("
					+ isCustomFrequency() + ");\n");
		}
		if (this.scriptEvaluator != null) {
			sb.append(blockVariable + ".setScriptName(\""
					+ this.getScriptName() + "\");\n");
			sb.append(scriptEvaluator.exportScript());
		}

		sb.append(blockVariable + ".init();\n");
		sb.append(blockVariable + ".setLocalTranslation(new Vector3f("
				+ blockTranslation.getX() + ", " + blockTranslation.getY()
				+ ", " + blockTranslation.getZ() + "));\n");

		Quaternion blockLocalRotation = this.getLocalRotation();
		if (blockLocalRotation.getX() != 0 || blockLocalRotation.getY() != 0
				|| blockLocalRotation.getZ() != 0) {
			sb.append(blockVariable + ".setLocalRotation(new Quaternion("
					+ blockLocalRotation.getX() + ", "
					+ blockLocalRotation.getY() + ", "
					+ blockLocalRotation.getZ() + ", "
					+ blockLocalRotation.getW() + "));\n");
		}
		for (String name : this.getParameters().keySet()) {
			sb.append(getExportVariable() + ".getParameters().put(\"" + name
					+ "\", " + this.getParameters().get(name) + ");\n");
		}

		return sb.toString();
	}

}
